<!-- 表单 》 结构树 -->

<template>
  <el-form-item v-bind="formItem">
    <el-input v-show="false" :value="CORE.isEmpty(model) ? '' : '1'" />

    <!-- 标签 -->
    <template v-if="hasLabelSlot" #label>
      <slot name="label" :label="formItem.label">{{ formItem.label }}</slot>
    </template>

    <el-cascader
      v-if="dict.length"
      ref="tree"
      v-model="model"
      v-bind="commonProps"
      :popper-class="`CoreWeb-popup-tree ${popClass}`"
      :options="treeData"
      :props="props"
      :separator="and"
      :filterable="!noSearch"
      :show-all-levels="allName"
      :clearable="!noClear && !initRoot && !initFirst"
      @change="emitChange"
    >
      <template #default="{ data }">
        <div class="CoreWeb-option">
          <slot :item="data">
            <span>{{ data.label }}</span>
            <span v-if="hasCount && data.children" class="CoreWeb-option-count">({{ data.children.length }})</span>
            <span class="CoreWeb-option-right">
              <span v-if="hasRight">{{ CORE.dict(nodeDict, data.value, rightIndex - 1) }}</span>
            </span>
          </slot>
        </div>
      </template>
    </el-cascader>

    <el-select
      v-else
      :value="undefined"
      v-bind="commonProps"
      :no-data-text="empty"
      :popper-class="`${className}-poper-empty`"
    />
  </el-form-item>
</template>

<script>
// ============================== 导入依赖 ============================== //

import CORE from '@/CORE'
import mixForm from '../mixForm'
import UTIL from './UTIL'

const name = 'FormTree'

// ============================== 导出组件 ============================== //

export default {
  /**
   * 名称定义 (实际使用名称)
   */
  name,

  /**
   * 混入列表 (配置选项合并)
   */
  mixins: [mixForm],

  /**
   * 属性注册 (抽取以便查阅)
   */
  props: CORE.WEB[name],

  /**
   * 状态数据 (函数返回对象)
   */
  data() {
    return {
      model: [], // 内部绑定值
      EMPTY: undefined, // 空值常量
    }
  },

  /**
   * 计算属性 (慎用箭头函数)
   */
  computed: {
    /**
     * 属性对象
     */
    props() {
      const { clickExpand, allId, onlyLeaf } = this
      return {
        expandTrigger: clickExpand ? 'click' : 'hover', // 鼠标悬停时展开
        emitPath: allId, // 选中时，是否返回路径 ID 列表
        checkStrictly: !onlyLeaf, // 取消选中关联，从而可选任意一级
      }
    },

    /**
     * 结构树数据
     */
    treeData() {
      const { dict, grayed } = this
      return UTIL.getTreeData([], dict, grayed)
    },

    /**
     * ID 列表的映射
     */
    idListMap() {
      const { dict } = this
      return UTIL.getIdListMap({}, dict)
    },

    /**
     * 名称列表的映射
     */
    nameListMap() {
      const { dict } = this
      return UTIL.getNameListMap({}, dict)
    },

    /**
     * 节点字典
     */
    nodeDict() {
      const { dict } = this
      return UTIL.getNodeDict([], dict)
    },

    /**
     * 根节点数据
     */
    rootModel() {
      const { dict } = this
      return dict.length ? [dict[0][1]] : []
    },

    /**
     * 首个叶子数据
     */
    firstModel() {
      const { dict } = this
      return UTIL.getFirstIdList([], dict)
    },
  },

  /**
   * 本地方法 (轻量重要在前)
   */
  methods: {
    /**
     * 是否不等 (可覆盖)
     */
    isDiff() {
      const { obj, prop, model, dict, init, initRoot, initFirst, allId } = this
      const value = obj[prop]

      // 无值 (如 undefined / null / '' / [] 等)
      if (CORE.isEmpty(value)) {
        if (dict.length && (init || initRoot || initFirst)) return true
        else return !CORE.isEmpty(model)
      }

      // 有值
      if (allId) return value.join() !== model.join()
      else return value !== model
    },

    /**
     * 设置内部绑定值 (可覆盖)
     */
    setModel() {
      const { obj, prop, init, allId, idListMap, dict, initRoot, initFirst, rootModel, firstModel } = this
      const value = obj[prop] || init

      let idList = (allId ? value : idListMap[value]) || []

      if (!idList.length && dict.length) {
        if (initRoot) idList = rootModel
        if (initFirst) idList = firstModel
      }

      this.model = allId ? idList : (idList[idList.length - 1] || null)
    },

    /**
     * 获取最终回传值 (可覆盖)
     */
    getValue() {
      const { model, allId, EMPTY } = this
      return allId ? model : (model || EMPTY)
    },

    /**
     * 是否相等 (可覆盖)
     */
    equals(value) {
      const { obj, prop, bindList, isMulti, model, allId, nodeDict, EMPTY, allName, nameListMap, and } = this

      // ID 不等
      if (JSON.stringify(obj[prop]) !== JSON.stringify(value)) return false
      // 只绑定了一个
      if (!isMulti) return true

      // 绑定了多个，ID 为空
      if (CORE.isEmpty(model)) return bindList.every((key, i) => obj[key.trim()] === (i ? '' : EMPTY))
      // 绑定了多个，ID 有值
      if (allId) {
        const values = Array(bindList.length).fill().map((_) => [])
        value.forEach((item) => {
          const matched = nodeDict.find((node) => node[0] === item) || []
          bindList.forEach((_, i) => values[i].push(matched[i]))
        })
        return bindList.every((key, i) => !i ? true : JSON.stringify(obj[key.trim()]) === JSON.stringify(values[i]))
      } else {
        const values = CORE.clone(nodeDict.find((node) => node[0] === model) || [])
        // 名称存全路径
        if (allName) values[1] = (nameListMap[model] || []).join(and)
        return bindList.every((key, i) => !i ? true : obj[key.trim()] === values[i])
      }
    },

    /**
     * 提交改变
     */
    emitChange() {
      const { obj, prop, bindList, isMulti, allId, allName, and, EMPTY, nodeDict, nameListMap } = this
      // 关闭下拉
      if (this.$refs.tree) this.$refs.tree.dropDownVisible = false

      // 获取新值
      const value = this.getValue()

      // 判断改变
      if (this.onlyShow || this.equals(value)) return

      // 更新绑定
      if (isMulti) {
        if (allId) {
          const values = Array(bindList.length).fill().map((_) => [])
          value.forEach((item) => {
            const matched = nodeDict.find((node) => node[0] === item) || []
            bindList.forEach((_, i) => values[i].push(matched[i]))
          })
          bindList.forEach((key, i) => this.$set(obj, key, values[i]))
          this.$emit('change', values)
        } else {
          const values = CORE.clone(nodeDict.find((node) => node[0] === value) || [])
          // 名称存全路径
          if (allName) values[1] = (nameListMap[value] || []).join(and)
          bindList.forEach((key, i) => this.$set(obj, key, values[i] || (i ? '' : EMPTY)))
          this.$emit('change', values)
        }
      } else {
        this.$set(obj, prop, value)
        this.$emit('change', value)
      }

      // 提交之后
      this.afterEmit()
    },
  },
}
</script>
